#include "cuda_runtime.h"
#include "device_launch_parameters.h"

#include "convertKernelCharInt.cuh"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SIZE	8 * 1024 * 1024

int convertChar4ToInt(char	*cbuf,
					  int	totalIntItems,
					  int	*output) {

	for (int i=0; i<totalIntItems; i++) {

		output[i] = 
			(cbuf[(i*4)] & 0xFF) | 
			((cbuf[(i*4) + 1] & 0xFF) << 8) | 
			((cbuf[(i*4) + 2] & 0xFF) << 16) | 
			((cbuf[(i*4) + 3] & 0xFF) << 24);

	}

	return 0;

}

void printCharBuf(char	*cbuf,
				  int	startindex,
				  int	totalItems) {

    printf ("Print Char Buffers\n");	
	printf ("------------------\n");	

	for (int i=startindex; i<totalItems; i++)
		printf("%c",cbuf[i]);
	printf("\n");
			
	printf ("-------END------\n");
}

void printIntBuf(int	*ibuf,
				 int	startindex,
				 int	totalItems) {

    printf ("Print Intuffers\n");	
	printf ("------------------\n");

	for (int i=startindex; i<totalItems; i++)
		printf("%x\n",ibuf[i]);
	
	printf ("-------END------\n");
}

void compareCharBuf(char	*srcbuf,
					char	*destbuf,
					int		startindex,
					int		totalItems) {

	int totaldiff = 0;

	for (int i=startindex; i<totalItems; i++) {
		if (srcbuf[i] != destbuf[i]) {

			if (totaldiff <= 5)
				printf ("Not match index %d, srcbuf= %d, destbuf=%d\n", i, srcbuf[i], destbuf[i]);
			
			totaldiff ++;
		}
	}
		
	printf ("Summary of comparison\n");
	printf ("---------------------\n");
	printf ("Total of items        : %d\n", totalItems);
	printf ("Total of bytes        : %d\n", totalItems * 4);
	printf ("Total of not match    : %d\n", totaldiff);
	printf ("-------END------\n");

}

void compareIntBuf(int	*srcbuf,
				   int	*destbuf,
				   int	startindex,
				   int	totalItems) {

	int totaldiff = 0;

	for (int i=startindex; i<totalItems; i++) {
		if (srcbuf[i] != destbuf[i]) {

			if (totaldiff <= 5)
				printf ("Not match index %d, srcbuf= %d, destbuf=%d\n", i, srcbuf[i], destbuf[i]);
			
			totaldiff ++;
		}
	}
		
	printf ("Summary of comparison\n");
	printf ("---------------------\n");
	printf ("Total of items        : %d\n", totalItems);
	printf ("Total of bytes        : %d\n", totalItems * 4);
	printf ("Total of not match    : %d\n", totaldiff);
	printf ("-------END------\n");

}

cudaError_t convertChar4ToIntWithCuda(char	*cbuf, 
									  int	totalItem, 
									  int	*output) {

    char *dev_cbuf;
	int *dev_output;
	
	cudaError_t cudaStatus;

	//Allocate GPU buffers for vectors
	cudaStatus = cudaMalloc((void**)&dev_cbuf, totalItem * sizeof(char));
	if( cudaStatus != cudaSuccess){
		fprintf(stderr, "cudaMalloc failed!");
		goto Error;
	}

	cudaStatus = cudaMalloc((void**)&dev_output, totalItem);
	if( cudaStatus != cudaSuccess){
		fprintf(stderr, "cudaMalloc failed!");
		goto Error;
	}

	//Copy input vectors from host memory to GPU buffers
	cudaStatus = cudaMemcpy(dev_cbuf, cbuf, totalItem, cudaMemcpyHostToDevice);
	if( cudaStatus != cudaSuccess){
		fprintf(stderr, "cudaMemcpy failed!");
		goto Error;
	}

	//Set allocated memory to 0
	cudaStatus = cudaMemset(dev_output, 0, totalItem);
	if( cudaStatus != cudaSuccess){
		fprintf(stderr, "cudaMemcpy failed!");
		goto Error;
	}

	//Lauch a matchKernel on the GPU with one thead for each element
	convertkernelChar4ToInt<<<65535, 256>>>(dev_cbuf, totalItem / 4, dev_output);

	// Check for any errors launching the kernel
    cudaStatus = cudaGetLastError();
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "BitMatchKernel launch failed: %s\n", cudaGetErrorString(cudaStatus));
        goto Error;
    }

	// cudaDeviceSynchronize waits for the kernel to finish, and returns
    // any errors encountered during the launch.
    cudaStatus = cudaDeviceSynchronize();
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaDeviceSynchronize returned error code %d after launching addKernel!\n", cudaStatus);
        goto Error;
    }

	//Copy output from GPU buffer to host memory
	cudaStatus = cudaMemcpy(output, dev_output, totalItem, cudaMemcpyDeviceToHost);
	if( cudaStatus != cudaSuccess){
		fprintf(stderr, "cudaMemcpy output failed!");
		goto Error;
	}

	Error:
		cudaFree(dev_cbuf);
		cudaFree(dev_output);

 return cudaStatus;
}

cudaError_t convertToChar4WithCuda(int *ibuf, 
								   int totalItem,
								   char *output){
	cudaError_t cudaStatus;
	int *dev_ibuf;
	char *dev_output;

	//Allocate GPU buffers for vectors
	cudaStatus = cudaMalloc((void**)&dev_output, totalItem);
	if( cudaStatus != cudaSuccess){
		fprintf(stderr, "cudaMalloc failed!");
		goto Error3;
	}

	cudaStatus = cudaMalloc((void**)&dev_ibuf, totalItem);
	if( cudaStatus != cudaSuccess){
		fprintf(stderr, "cudaMalloc failed!");
		goto Error3;
	}

	//Set allocated memory to 0
	cudaStatus = cudaMemset(dev_output, 0, totalItem);
	if( cudaStatus != cudaSuccess){
		fprintf(stderr, "cudaMemcpy failed!");
		goto Error3;
	}

	//Copy input vectors from host memory to GPU buffers
	cudaStatus = cudaMemcpy(dev_ibuf, ibuf, totalItem, cudaMemcpyHostToDevice);
	if( cudaStatus != cudaSuccess){
		fprintf(stderr, "cudaMemcpy failed!");
		goto Error3;
	}

	//Lauch a matchKernel on the GPU with one thead for each element
	convertkernelIntToChar4<<<65535, 128>>>(dev_ibuf, totalItem / 4, dev_output);

	// Check for any errors launching the kernel
    cudaStatus = cudaGetLastError();
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "BitMatchKernel launch failed: %s\n", cudaGetErrorString(cudaStatus));
        goto Error3;
    }

	// cudaDeviceSynchronize waits for the kernel to finish, and returns
    // any errors encountered during the launch.
    cudaStatus = cudaDeviceSynchronize();
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaDeviceSynchronize returned error code %d after launching intMatchKernel!\n", cudaStatus);
        goto Error3;
    }

	//Copy output from GPU buffer to host memory
	cudaStatus = cudaMemcpy(output, dev_output, totalItem, cudaMemcpyDeviceToHost);
	if( cudaStatus != cudaSuccess){
		fprintf(stderr, "cudaMemcpy output failed!");
		goto Error3;
	}

	//for (int i=0; i<sizeofinput; i++) {
	//  if (output[i] == 0) {
	//	printf("xxxxx>i=%d, result=%d \n", i, output[i]);
	//  }
	//}

Error3:
 cudaFree(dev_output);
 cudaFree(dev_ibuf);

 return cudaStatus;
}

int main() 
{
	
	char *cbufInput;
	int *ibuf;

	int totalIntItems = SIZE / 4;

	cbufInput = (char*)malloc(SIZE);
	ibuf = (int*)malloc(SIZE);

	int bufchar = 65;
	for (int i=0; i<SIZE; i++) {

		cbufInput[i] = bufchar;
		
		if (bufchar > 67)
			bufchar = 65;
		else
			bufchar++;	
	}

	//printCharBuf(cbufInput, SIZE-20, SIZE);
	convertChar4ToInt(cbufInput, totalIntItems, ibuf);
	//printIntBuf(ibuf, totalIntItems - 10, totalIntItems);
	
	int *gpuOutputIntBuf;
	gpuOutputIntBuf = (int*)malloc(SIZE);
	convertChar4ToIntWithCuda(cbufInput, SIZE, gpuOutputIntBuf);
	//printIntBuf(gpuOutputIntBuf, totalIntItems - 20, totalIntItems);
	//printIntBuf(gpuOutputIntBuf, 0, 20);
	compareIntBuf(ibuf, gpuOutputIntBuf, 0, totalIntItems);

	char *gpuOutputCharBuf;
	gpuOutputCharBuf = (char*)malloc(SIZE);
	convertToChar4WithCuda(gpuOutputIntBuf, SIZE, gpuOutputCharBuf);
	printCharBuf(gpuOutputCharBuf, 0, 20);
	printCharBuf(gpuOutputCharBuf, SIZE - 20, SIZE);
	compareCharBuf(cbufInput,gpuOutputCharBuf, 0, SIZE);

	free(ibuf);
	free(cbufInput);
	free(gpuOutputIntBuf);

	return 0;

}